
#include <unordered_map>
#include <map>
#include "data.h"

using namespace std;


#define debug 1
// #undef debug

struct Product_Status{
  int status, step, now_time, idx;
  Product_Status(){}
  Product_Status(int _p1, int _p2, int _p3, int _p4):status(_p1),step(_p2),now_time(_p3),idx(_p4){}
  int& operator[] (int x) {
    if (x==0) return status;
    if (x==1) return step;
    if (x==2) return now_time;
    if (x==3) return idx;
  }
};

struct Period{
  string product_id, equip_name;
  int route_id, start_time, duration_time, end_time;
  Period(string _p1, int _p2, string _p3, 
        int _p4, int _p5, int _p6):
          product_id(_p1), route_id(_p2), equip_name(_p3), start_time(_p4), duration_time(_p5), end_time(_p6) {}
  bool operator< (const Period& rhs) const {
    if (product_id!=rhs.product_id) return product_id<rhs.product_id;
    if (route_id!=rhs.route_id) return route_id<rhs.route_id;
    return start_time<rhs.start_time;
  }
  friend ostream& operator<<(ostream &out, const Period & period) {
    out<<period.product_id<<",";
    out<<period.route_id<<",";
    out<<period.equip_name<<",";
    out<<period.start_time/60.0<<",";
    out<<period.duration_time/60.0<<",";
    out<<period.end_time/60.0;
    return out;
  }
};



struct Greedy {
  Data *data;
  vector<Period> product_dispatchs;
  map<string,int> mp{{"A1", 0}, {"B1",1},{"C1",2},{"D1",3}};
  map<string, pair<int,int>> T3_machine, T4_machine;
  map<string, pair<int,vector< pair<int,int> > >  > T5_machine;
  typedef std::vector<int> Order;
  Order& order;
  vector<Product_Status> product_status;
  vector<int> T5_task_list;

  Greedy(Data *_data, Order &_order):data(_data),order(_order){
    LOG_DEBUG({for(auto &now:order) cout<<now<<",";cout<<endl;})
    for(auto &equip: data->equips) {
      if (equip.equip_type[3]=='3') {
        T3_machine[equip.equip_name] = {0,-1};
      } else if (equip.equip_type[3]=='4') {
        T4_machine[equip.equip_name] = {0,-1};
      } else if (equip.equip_type[3]=='5') {
        T5_machine[equip.equip_name] = {0, {{0, 0}, {100000000, 100000000}}};
      }
    }
    LOG_DEBUG(cout<<"Greedy init finish"<<endl;)
  }

  inline int find_early_time(bool flag, int product_idx) {
    auto early_time = 10000000;
    if (flag) {
      for(auto &item:T3_machine) {
        auto pp_time = (item.second.second == product_idx)?0:30;
        early_time = min(early_time, item.second.first + pp_time); 
      }
    } else {
      for(auto &item:T4_machine) {
        auto pp_time = (item.second.second == product_idx)?0:30;
        early_time = min(early_time, item.second.first + pp_time); 
      }
    }
    LOG_DEBUG(cout<<"find_early_time finish,return:["<<early_time<<"]"<<endl;)
    return early_time;
  }

  pair<int,int> get_best_end_time(int now_time, int product_time, int product_idx, int product_step) {
    LOG_DEBUG(cout<<"get_best_end_time begin"<<endl;)
    auto &product = data->products[product_idx];
    auto &route = data->routes[mp[product.route_id]];
    auto B_duration = route.cost_time_low[product_step];
    auto C_duration = 12 * product.section_num;
    auto early_time = find_early_time(route.equip_types[product_step][3] == '3', product_idx);
    // time = map(product_time, early_time) + B_duration;
    auto tmp_time = max(max(product_time, early_time) + B_duration, now_time);
    LOG_DEBUG(cout<<"get_best_end_time finish, return:["<<(tmp_time!=now_time)<<" "<<tmp_time+C_duration<<"]"<<" B_duration="<<B_duration<<endl;)
    return {tmp_time!=now_time, tmp_time+C_duration};
  }
  int find_work_product(int now_time) {
    LOG_DEBUG(cout<<"find_work_product begin"<<endl;)
    int pos=-1;
    // first = not_tight, second = end_time;
    pair<int,int> tight = {1,100000000};
    for(int i=0,r=product_status.size();i<r;i++) {
      if (product_status[i][0]!=1) continue;
      auto tmp_tight = get_best_end_time(now_time, product_status[i][2], product_status[i][3], product_status[i][1]);
      if (tmp_tight < tight) {
        pos = i;
        tight = tmp_tight;
      }
    }
    if (tight.first == 1) {
      std::vector<int> vec;
      for(int i=0,r=product_status.size();i<r;i++) {
        if (product_status[i][0]) continue;
        vec.push_back(i);
        if (vec.size()>3) break;
      }
      if (vec.size()<=3) {
        for(auto idx:vec) product_status[idx][0]=1;
      } else 
        if (vec.size()) product_status[vec[0]][0] =1;
      LOG_DEBUG(cout<<"find_work_product finish, return:["<<(vec.size()>0?vec[0]:-1)<<"]"<<endl;)
      return vec.size()>0?vec[0]:pos;
    }
    LOG_DEBUG(cout<<"find_work_product finish, return:["<<pos<<"]"<<endl;)
    assert(pos!=-1);
    return pos;
  } 

  int update_status(int product_status_idx, int now_time) {
    LOG_DEBUG(cout<<"update_status begin"<<endl;)
    auto product_idx = product_status[product_status_idx][3];
    int product_time = product_status[product_status_idx][2];
    auto product_step = product_status[product_status_idx][1];
    auto &product = data->products[product_idx];
    auto &route = data->routes[mp[product.route_id]];
    int B_duration = route.cost_time_low[product_step];
    auto C_duration = 12 * product.section_num;
    LOG_DEBUG(cout<<"update_status begin, product_time="<<product_time<<", now_time="<<now_time<<endl;)
    LOG_DEBUG(cout<<"B_duration]="<<B_duration<<endl;)
    LOG_DEBUG(cout<<"product_step="<<product_step<<",limit="<<route.cost_time_low.size()<<endl;)
    auto early_time = find_early_time(route.equip_types[product_step][3] == '3', product_idx);
    auto time = max(product_time, early_time) + B_duration;
    auto last_time = max(time, now_time);
    auto B_start_time = last_time - B_duration;
    auto B_end_time = last_time;
    auto waste_time = 10000000;
    string machine_name;
    if (route.equip_types[product_step][3] == '3') {
      for (auto &item: T3_machine) {
        auto pp_time = item.second.second == product_idx?0:30;
        if (item.second.first + pp_time <= B_start_time and B_start_time - item.second.first < waste_time) {
          waste_time = B_start_time - item.second.first;
          machine_name = item.first;
        }
      }
      T3_machine[machine_name] = {B_end_time, product_idx};
    } else {
      for (auto &item: T4_machine) {
        auto pp_time = item.second.second == product_idx?0:30;
        if (item.second.first + pp_time <= B_start_time and B_start_time - item.second.first < waste_time) {
          waste_time = B_start_time - item.second.first;
          machine_name = item.first;
        }
      }
      T4_machine[machine_name] = {B_end_time, product_idx};
    }
    product_dispatchs.emplace_back(product.product_id, product_step + 1, machine_name, B_start_time, B_duration, B_end_time);
    for(int i=0,r=product.section_num;i<r;i++) {
      product_dispatchs.emplace_back(product.product_id, product_step + 2, std::string("Y-2045"), B_end_time + 12 * i, 12, B_end_time + 12 * (i + 1));
    }
    product_status[product_status_idx][1] += 2;
    product_status[product_status_idx][2] = last_time + C_duration;
    if (product_status[product_status_idx][1] == route.cost_time_low.size()) {
      product_status[product_status_idx][0] = 2;
      puts("warning some product goes wrong!");
    } else if (route.equip_types[product_status[product_status_idx][1]] == "T-05") {
      if (product_status[product_status_idx][1] >= 12) {
        T5_task_list.push_back(product_status_idx);
        product_status[product_status_idx][0] = 2;
      } else {
        update_D_machine_immediate(product_status_idx);
      }
    }
    LOG_DEBUG(cout<<"update_status finish, return:["<<last_time<<"+"<<C_duration<<"]"<<endl;)
    return last_time + C_duration;
  }
  void update_D_machine_immediate(int product_status_idx) {
    auto &step = product_status[product_status_idx].step;
    int &time = product_status[product_status_idx].now_time;
    auto &idx = product_status[product_status_idx].idx;
    auto &product = data->products[idx];
    auto &route = data->routes[mp[product.route_id]];
    auto &duration = route.cost_time_low[step];
    auto final_time = 0;
    LOG_DEBUG(cout<<"T1: product_status_idx="<<product_status_idx<<endl;)
    for(int i=0;i<product.section_num;i++) {
      string machine_name;
      int start_time=400000000;
      vector<pair<int,int>>::iterator pos;
      LOG_DEBUG(cout<<"T2"<<endl;)
      for(auto &item:T5_machine) {
        auto tmp_pos = item.second.second.end();
        for(auto it=item.second.second.begin()+1;it!=item.second.second.end();it++) {
          LOG_DEBUG(cout<<"tmp_pos:"<<it->first<<" "<<(it-1)->second<<" "<<time<<endl;)
          if (it->first - duration < max((it-1)->second,time)) continue;  
          tmp_pos = it;
          break;
        }
        if (max((tmp_pos-1)->second,time) < start_time) {
          start_time = max((tmp_pos-1)->second,time);
          machine_name = item.first;
          pos = tmp_pos;
        }
      }
      LOG_DEBUG(cout<<"T3"<<endl;)
      int end_time = start_time + duration;
      LOG_DEBUG(cout<<"|"<<end_time<<"|"<<machine_name<<endl;)
      assert(machine_name.length()>0);
      T5_machine[machine_name].first = end_time;
      T5_machine[machine_name].second.insert(pos, {start_time, end_time});
      product_dispatchs.emplace_back(product.product_id, step + 1, machine_name, start_time, duration, end_time);
      final_time = max(final_time, end_time);
      LOG_DEBUG(cout<<"T4"<<endl;)
    }
    LOG_DEBUG(cout<<"T5"<<endl;)
    product_status[product_status_idx][1]++;
    product_status[product_status_idx][2] = final_time;
    assert(final_time<=100000000);
    LOG_DEBUG(cout<<"T6:"<<final_time<<endl;)
    LOG_DEBUG(cout<<"update_D_machine_immediate finish"<<endl;)
  }

  int solve() {
    auto is_finished = [](std::vector<Product_Status> &a) {
      for(auto &now:a) {
        if (now[0]!=2) return false;
      }
      return true;
    };
    for(int i=0,r=order.size();i<r;i++) {
      auto idx = order[i];
      if (i%2==0) 
        product_dispatchs.emplace_back(data->products[idx].product_id,1,std::string("Z-1001"),240*(i/2),240,240*(i/2+1));
      else 
        product_dispatchs.emplace_back(data->products[idx].product_id,1,std::string("Z-1002"),240*(i/2),240,240*(i/2+1));
      product_status.emplace_back(0,1,240*(i/2+1),idx);
    }
    LOG_DEBUG(cout<<"solve order finish, product_status.size()="<<product_status.size()<<", product_dispatchs.size()="<<product_dispatchs.size()<<endl;)
    int now_time = 0;
    while (!is_finished(product_status)) {
      auto product_status_idx = find_work_product(now_time);
      now_time = update_status(product_status_idx, now_time);
      LOG_DEBUG(cout<<"while finish one, now_time="<<now_time<<endl;)
    }
    LOG_DEBUG(cout<<"solve find_work_product-update_status finish"<<endl;)
    //update_D_machine
    for (auto &product_status_idx: T5_task_list) {
      update_D_machine_immediate(product_status_idx);
    }
    LOG_DEBUG(cout<<"solve update_D_machine_immediate all finish"<<endl;)
    sort(product_dispatchs.begin(), product_dispatchs.end());
    auto ans=0;
    for(auto &period: product_dispatchs) 
      ans = max(ans, period.end_time);
    LOG_DEBUG(cout<<"solve finish,return["<<ans<<"]"<<endl;)
    return ans;
  }
  friend ostream& operator<<(ostream &out, const Greedy & greedy) {
    out<<"product_id,route_No,equ_name,start,duration,end\n";
    for(auto &period: greedy.product_dispatchs) {
      out<<period<<"\n";
    }
    return out;
  }
};
